// SPDX-License-Identifier: GPL-2.0
#include <seminix/linkage.h>
#include <seminix/syscall.h>
#include <seminix/nospec.h>
#include <asm/esr.h>
#include <asm/traps.h>
#include <asm/daifflags.h>
#include <asm/unistd.h>

asmlinkage long sys_ni_syscall(void);

static long do_ni_syscall(struct pt_regs *regs, long scno)
{
    return sys_ni_syscall();
}

static long __invoke_syscall(struct pt_regs *regs, syscall_fn_t syscall_fn)
{
    return syscall_fn(regs);
}

static void invoke_syscall(struct pt_regs *regs, unsigned int scno,
               unsigned int sc_nr,
               const syscall_fn_t syscall_table[])
{
    long ret;

    if (scno < sc_nr) {
        syscall_fn_t syscall_fn;
        syscall_fn = syscall_table[array_index_nospec(scno, sc_nr)];
        ret = __invoke_syscall(regs, syscall_fn);
    } else {
        ret = do_ni_syscall(regs, scno);
    }

    regs->regs[0] = ret;
}

static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr,
               const syscall_fn_t syscall_table[])
{
    regs->orig_x0 = regs->regs[0];
    regs->syscallno = scno;

    local_daif_restore(DAIF_PROCCTX);

    invoke_syscall(regs, scno, sc_nr, syscall_table);

    local_daif_mask();
}

static inline void sve_user_discard(void)
{
    if (!system_supports_sve())
        return;

    clear_thread_flag(TIF_SVE);

    /*
     * task_fpsimd_load() won't be called to update CPACR_EL1 in
     * ret_to_user unless TIF_FOREIGN_FPSTATE is still set, which only
     * happens if a context switch or kernel_neon_begin() or context
     * modification (sigreturn, ptrace) intervenes.
     * So, ensure that CPACR_EL1 is already correct for the fast-path case.
     */
    sve_user_disable();
}

asmlinkage void el0_svc_handler(struct pt_regs *regs)
{
    sve_user_discard();
    el0_svc_common(regs, regs->regs[8], __NR_syscalls, sys_call_table);
}
